package satellite

import (
	"fmt"
	"io"
	"sync"
	"sync/atomic"

	"gddgame.cc/galaxy/utils/uuid"
)

type SessionConn interface {
	Connected() bool
	OnClose(conn Conn)
	OnAccept(conn Conn, code byte, message Message) error
	Close()
}

type Server interface {
	NewConn(session string) SessionConn
	GetSession(code byte, packet *Packet) (string, error)
}

type gateSocket struct {
	*Socket

	compress   Compress
	cryptoType CryptoType
	protocol   Protocol
}

func (g *gateSocket) SetCompress(compress Compress) {
	g.compress = compress
}

func (g *gateSocket) SetCrypto(cryptoType CryptoType) {
	g.cryptoType = cryptoType
}

func (g *gateSocket) read(conn Conn) (byte, *Packet, error) {
	var isCompress bool
	packet, err := g.protocol.Read(conn)
	if err != nil {
		return 0, nil, err
	}
	// 处理头: 解析压缩状态
	code := packet.Head[0] >> 1
	if int(packet.Head[0]&1) > 0 {
		isCompress = true
	} else {
		isCompress = false
	}
	//fmt.Println(packet.Head[0], code, isCompress)
	if isCompress && g.compress != nil {
		packet.onRead(func() error {
			packet.reader = g.compress.Reader(packet.reader)
			return nil
		})
		// 回收compress read
		packet.onClose(func() {
			if packet.reader != nil {
				g.compress.ReleaseReader(packet.reader)
			}
		})
	}

	return code, packet, nil
}
func (g *gateSocket) Write(code byte, conn Conn, compress bool, handler func(writer io.Writer) error) error {
	var w io.WriteCloser
	if compress {
		a := handler
		handler = func(writer io.Writer) error {
			w = g.compress.Writer(writer)
			if err := a(w); err != nil {
				return err
			}
			return w.Close()
		}
		code = byte(code<<1&0xff) | byte(1&1)
	} else {
		code = byte(code<<1&0xff) | byte(0&1)
	}
	_, err := g.protocol.Write([]byte{code}, conn, handler)

	// 回收compress write
	if compress && w != nil {
		g.compress.ReleaseWriter(w)
	}
	return err
}

type Gateway struct {
	*Socket
	*gateSocket

	server Server

	sessionMap sync.Map
	sessionNum int32
	connNum    int32
}

func NewGateway(server Server) *Gateway {
	g := Gateway{
		Socket:     NewSocket(),
		sessionMap: sync.Map{},
	}
	g.gateSocket = &gateSocket{
		Socket:   g.Socket,
		protocol: NewVariProtocol(1),
	}
	g.server = server
	g.compress = nil
	g.close = make(chan struct{}, 1)
	return &g
}

func (g *Gateway) Listen() error {
	return g.Socket.Listen(func(accept <-chan Conn) {
		go func() {
			for {
				select {
				case conn := <-accept:
					go g.newConn(conn)
				case <-g.close:
					return
				}
			}

		}()
	})
}

func (g *Gateway) SessionNum() int32 {
	return g.sessionNum
}

func (g *Gateway) ConnNum() int32 {
	return g.connNum
}

func (g *Gateway) newConn(conn Conn) {
	if g.closed {
		return
	}
	atomic.AddInt32(&g.connNum, 1)
	var sessionConn SessionConn
	var session string

	for {
		code, packet, err := g.read(conn)
		if err != nil {
			break
		}
		if g.closed {
			_ = packet.Close()
			break
		}
		if sessionConn == nil {
			session, err = g.server.GetSession(code, packet)
			if err != nil {
				g.Error("[ Satellite ] open sid", err.Error())
			}
			if session != "" {
				old, ok := g.sessionMap.Load(session)
				if ok {
					sessionConn = old.(SessionConn)
					g.Debugf("[ Satellite ] old client:%#v, %#v", session, conn.Transport().Type())
				} else {
					g.Error("[ Satellite ] old client not exist")
				}
			}
			if sessionConn == nil {
				session = uuid.RandomString()
				sessionConn = g.server.NewConn(session)
				g.sessionMap.Store(session, sessionConn)
				atomic.AddInt32(&g.sessionNum, 1)
				g.Debugf("[ Satellite ] new client:%#v", session)
			}
		}
		if err := sessionConn.OnAccept(conn, code, packet); err != nil {
			break
		}
		if err := packet.Close(); err != nil {
			break
		}

		if g.closed {
			break
		}
		if !conn.Connected() {
			break
		}
	}

	// 断开链接
	if sessionConn != nil && sessionConn.Connected() {
		sessionConn.OnClose(conn)
		g.Debugf("[ Satellite ] Close conn:%#v, %#v", session, conn)
	}
	if conn.Connected() {
		_ = conn.Close()
	}
	atomic.AddInt32(&g.connNum, -1)
}

func (g *Gateway) Close() error {
	if g.closed {
		return nil
	}
	g.sessionNum = 0
	g.connNum = 0
	g.sessionMap.Range(func(key, value interface{}) bool {
		value.(SessionConn).Close()
		return true
	})
	return g.Socket.Close()
}

func (g *Gateway) Disconnect(session string) {
	g.sessionMap.Delete(session)
	atomic.AddInt32(&g.sessionNum, -1)
}

type ConnectStatus int

const (
	CONNECTING ConnectStatus = iota
	CONNECTED
	DISCONNECTING
	DISCONNECTED
	RECONNECTING
)

type GatewayClientListener interface {
	OnConnect(conn Conn, success bool)
	Dispatch(conn Conn, code byte, message Message)
}

type GatewayClient struct {
	*Socket
	*gateSocket

	listener GatewayClientListener

	conn Conn

	connectStatus ConnectStatus
}

func NewGatewayClient(listener GatewayClientListener) *GatewayClient {
	client := &GatewayClient{
		Socket:        NewSocket(),
		listener:      listener,
		connectStatus: DISCONNECTED,
	}
	client.gateSocket = &gateSocket{
		Socket:   client.Socket,
		protocol: NewVariProtocol(1),
	}
	return client
}

func (s *GatewayClient) Dial() error {
	s.Debugf("[ Socket ] connect")
	if s.IsRunning() {
		return fmt.Errorf("Socket is running")
	}
	return s.Socket.Dial(func(conn Dial) {
		conn.Hook(s)
	})
}

func (s *GatewayClient) OnConnect(conn Dial) {
	go func() {
		for {
			code, packet, err := s.read(conn)
			if err != nil {
				conn.Error(err)
				return
			}
			if !s.IsRunning() {
				_ = packet.Close()
				_ = conn.Close()
				return
			}
			s.listener.Dispatch(conn, code, packet)
			if err := packet.Close(); err != nil {
				conn.Error(err)
				return
			}
		}
	}()
	s.listener.OnConnect(conn, true)
	// 下一次UpdateState中判断最新状态
	s.conn = conn
}

func (s *GatewayClient) OnDisconnect(conn Dial) {
	s.conn = nil
	s.UpdateState()
}

func (s *GatewayClient) OnError(conn Dial, err error) {
	if !s.IsConnected() {
		s.listener.OnConnect(conn, false)
	}
}

func (s *GatewayClient) IsRunning() bool {
	switch s.connectStatus {
	case CONNECTED:
	case CONNECTING:
	case RECONNECTING:
		return true
	default:
	}
	return false
}

func (s *GatewayClient) IsConnected() bool {
	return s.connectStatus == CONNECTED
}

func (s *GatewayClient) State() ConnectStatus {
	return s.connectStatus
}

// 在完成协议链接后执行，更新链接状态
func (s *GatewayClient) UpdateState() bool {
	// 运行后执行，isRuning
	if !s.IsRunning() {
		return false
	}
	// 运行后，才能确保是需要重连
	if s.conn == nil {
		if s.connectStatus == CONNECTED {
			s.connectStatus = RECONNECTING
		}
	} else {
		if s.connectStatus == RECONNECTING || s.connectStatus == CONNECTING {
			s.connectStatus = CONNECTED
		}
	}
	return true
}

func (s *GatewayClient) OnClose(reason string) error {
	if s.conn != nil {
		_ = s.conn.Close()
	}
	s.conn = nil
	s.connectStatus = DISCONNECTED
	return s.Socket.Close()
}

func (s *GatewayClient) SelectConn(transportType TransportType) Conn {
	return s.conn
}

func (s *GatewayClient) WriteSelect(code byte, compress bool, handler func(writer io.Writer) error) error {
	return s.Write(code, s.conn, compress, handler)
}
